/**
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 *
 * This file is a part of the CANN Open Software.
 * Licensed under CANN Open Software License Agreement Version 1.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */


#ifndef ATVC_HOST_REDUCE_OP_TILING_H
#define ATVC_HOST_REDUCE_OP_TILING_H
#include <algorithm>
#include <vector>
#include "common/compile_info.h"
#include "tiling_def.h"
#include "reduce/common/patterns.h"
#include "common/ops_utils.h"

namespace OpTiling
{
constexpr static uint64_t WORKSPACE_SIZE = 16 * 1024 * 1024;  // fixed workspace size for ascendc
constexpr static int32_t BASIC_BLOCK = 48 * 1024;             // 待删除
constexpr static int32_t CACHE_SIZE = 16 * 1024;              // cahce size for ub reduce
constexpr static int32_t MAX_INNER_A = 128;
constexpr static double THRES_HOLD = 0.85;
constexpr static int32_t A_STEP_LEN = 4;
static void MakeWrapDim(const std::vector<int64_t>& shape, std::vector<int64_t>& axes)
{
    // EnsureNotScalar at least return 1-D Tensor, so shapeSize cannot be 0
    size_t shapeSize = shape.size();
    for (size_t i = 0; i < axes.size(); i++) {
        if (axes[i] < 0) {
            axes[i] += shapeSize;
        }
    }
}

class ReduceOpTiling
{
public:
ReduceOpTiling(ATVC::ReduceTilingInputParam& inputParam,
    ATVC::ReducePolicy* policy, ATVC::ReduceParam* param)
    : param_(param), policy_(policy), opInput_(inputParam) {
        compileInfo_ = ATVC::GetOpCompileInfo();
    };

int32_t Run()
{
    MakeWrapDim(opInput_.reduceShape, opInput_.reduceDim);
    if (IsAxesValid(opInput_.reduceShape, opInput_.reduceDim) == -1) {
        return -1;
    }
    std::vector<uint64_t> newShape(MAX_DIM, 1);
    int32_t newShapeSize = 0;
    EliminateOne(opInput_.reduceShape, opInput_.reduceDim, newShape, newShapeSize);
    MergeAxis(opInput_.reduceDim, newShape, newShapeSize);
    if (!DoTiling(newShape, newShapeSize)) {
        printf("Do tiling failed!\n");
        return -1;
    }
    CalcWorkSpace();
    printf("ReduceOpTiling Run success!\n");
    return 0;
}

private:
template <class Pattern>
void ComputeStride(std::vector<uint64_t>& shape)
{
    uint64_t s = 1;
    uint64_t ds = 1;
    for (int32_t dim = Pattern::Dim - 1; dim > -1; dim--) {
        param_->tilingData.stride[dim] = s;
        param_->tilingData.dstStride[dim] = ds;
        s *= shape[dim];
        if (IsAxisA<Pattern>(dim)) {
            ds *= shape[dim];
        }
    }
    double meanVar = static_cast<double>(1) / static_cast<double>(s / ds);
    param_->tilingData.outSize = ds;
    param_->tilingData.meanVar = static_cast<float>(meanVar);
}

template <class Pattern>
void SetTilingKey()
{
    uint64_t groupR = param_->tilingData.groupR;
    uint32_t aCount = 0;
    uint32_t rCount = 0;
    uint32_t innerACount = 0;
    uint32_t innerRCount = 0;
    if (groupR == 1) {
        // normal case
        aCount = (unitA_.idx - (Pattern::FirstA ? 0 : 1)) / CONST2 + 1;
        innerRCount = (unitR_.idx - (Pattern::FirstA ? 1 : 0)) / CONST2 + 1;
    } else if (groupR <= compileInfo_.vectorCoreNum / CONST2) {
        // group case
        aCount = (unitA_.idx - (Pattern::FirstA ? 0 : 1)) / CONST2 + 1;
        rCount = (unitR_.idx - (Pattern::FirstA ? 1 : 0)) / CONST2 + 1;
        rCount = rCount + aCount;
    } else {
        // atomic case
        rCount = (unitR_.idx - (Pattern::FirstA ? 1 : 0)) / CONST2 + 1;
        innerACount = (unitA_.idx - (Pattern::FirstA ? 0 : 1)) / CONST2 + 1;
    }

    uint32_t innerID = Pattern::TailA ? 0 : 1;
    policy_->patternID = Pattern::ID * CONST10 + innerID;
    policy_->loopARCount = aCount * CONST10 + rCount;
    policy_->loopInnerARCount = innerACount * CONST10 + innerRCount;
}

void CalcWorkSpace()
{
    size_t spaceSize = 0;
    uint64_t groupR = param_->tilingData.groupR;
    uint64_t outSize = param_->tilingData.outSize;
    int32_t size = ge::GetSizeByDataType(opInput_.promoteDtpye);
    if (groupR > 1) {
        spaceSize = OpsUtils::CeilAlign(outSize * compileInfo_.vectorCoreNum * size, compileInfo_.ubBlockSize);
    }
    param_->workspaceSize = WORKSPACE_SIZE + spaceSize;
}

int32_t IsAxesValid(const std::vector<int64_t>& shape, const std::vector<int64_t>& axes)
{
    size_t shapeSize = shape.size();
    size_t axesSize = axes.size();
    if (axesSize > shapeSize) {
        printf("[ERROR] axis size is greater than shape size\n");
        return -1;
    };

    for (size_t i = 0; i < axesSize; i++) {
        if (axes[i] >= static_cast<int64_t>(shapeSize) || axes[i] < 0) {
            printf("[ERROR] axis size incorrect \n");
            return -1;
        };
    }
    return 0;
}

void EliminateOne(const std::vector<int64_t>& oriShape, std::vector<int64_t>& axes, std::vector<uint64_t>& shape,
                                  int32_t& shapeSize)
{
    int32_t dstIdx = 1;  // shape中第一个数给了1, 跳过第一个数
    for (size_t i = 0; i < axes.size(); i++) {
        // 前面补了一维，所有的axes需要加1
        axes[i] = axes[i] + 1;
    }
    int32_t eraseNum = 0;
    for (size_t i = 0; i < oriShape.size(); i++) {
        auto iter = std::find(axes.begin(), axes.end(), i + 1);
        if (oriShape[i] != 1) {
            shape[dstIdx++] = oriShape[i];
            if (iter != axes.end()) {
                *iter = *iter - eraseNum;
            }
        } else {
            eraseNum++;
            if (iter != axes.end()) {
                axes.erase(iter);
            }
        }
    }
    shapeSize = dstIdx;
}

void MergeAxis(std::vector<int64_t>& axes, std::vector<uint64_t>& shape, int32_t& shapeSize)
{
    int32_t tmpSize = 0;
    for (int32_t i = 0; i < shapeSize;) {
        auto iter0 = std::find(axes.begin(), axes.end(), i);
        bool isRAxis0 = iter0 != axes.end();
        uint64_t s = shape[i];
        int32_t j = i + 1;
        for (; j < shapeSize; j++) {
            auto iter1 = std::find(axes.begin(), axes.end(), j);
            bool isRAxis1 = iter1 != axes.end();
            if (isRAxis0 != isRAxis1) {
                break;
            }
            s *= shape[j];
            if (isRAxis1) {
                // 连续的R轴, 需要擦除后续R轴的索引
                axes.erase(iter1);
            }
        }
        i = j;
        shape[tmpSize++] = s;
        if (isRAxis0) {
            *iter0 = tmpSize - 1;
        }
    }
    for (int32_t i = tmpSize; i < shapeSize; i++) {
        shape[i] = 0;
    }
    shapeSize = tmpSize;
}

bool DoTiling(std::vector<uint64_t>& shape, int32_t shapeSize)
{
    switch (shapeSize) {
        case CONST1:
            return ComputeTiling<ATVC::ReducePattern::A>(shape);
            param_->tilingData.coreNum = 1; // A 場景：不用reduce 只用1個core copydata
        case CONST2:
            return ComputeTiling<ATVC::ReducePattern::AR>(shape);
        case CONST3:
            return ComputeTiling<ATVC::ReducePattern::ARA>(shape);
        case CONST4:
            return ComputeTiling<ATVC::ReducePattern::ARAR>(shape);
        case CONST5:
            return ComputeTiling<ATVC::ReducePattern::ARARA>(shape);
        case CONST6:
            return ComputeTiling<ATVC::ReducePattern::ARARAR>(shape);
        case CONST7:
            return ComputeTiling<ATVC::ReducePattern::ARARARA>(shape);
        case CONST8:
            return ComputeTiling<ATVC::ReducePattern::ARARARAR>(shape);
        case CONST9:
            return ComputeTiling<ATVC::ReducePattern::ARARARARA>(shape);
        default:
            return false;
    }
}

template <class Pattern>
bool ComputeExtraUnitA(const std::vector<uint64_t>& shape)
{
    // if RAxis is fully loaded in UB, and basicBlock is not enough, we need to calculate extra unitA
    if (unitR_.idx != -1 || Pattern::ID == PATTERN_A) {
        return true;
    }
    uint64_t innerR = unitR_.inner;
    uint64_t innerA = unitA_.inner / unitA_.step;  // restore original innerA
    uint64_t len = unitA_.idx == cBlock_.axis ? cBlock_.cacheLineOuter : shape[unitA_.idx];
    uint64_t outerA = unitA_.outer / OpsUtils::CeilDiv(len, unitA_.step) * len;
    uint64_t dTypeSize = ge::GetSizeByDataType(opInput_.inputDtype);
    uint64_t promoteDtypeSize = ge::GetSizeByDataType(opInput_.promoteDtpye);
    if (dTypeSize == 0 || promoteDtypeSize == 0) {
        printf("[Reduce Tiling] input dtype size cannot be zero!\n");
        return false;
    }
    uint64_t bBlockNum = basicBlock_ / dTypeSize;
    uint64_t maxInnerA = CACHE_SIZE / promoteDtypeSize;
    uint64_t step = 1;
    int32_t iA;
    for (iA = unitA_.idx; iA > -1; iA -= CONST2) {
        uint64_t axislen = (iA == cBlock_.axis ? cBlock_.cacheLineOuter : shape[iA]);
        bool splitHere = false;
        for (step = (iA == unitA_.idx ? unitA_.step : 1); step <= axislen; step += 1) {
            uint64_t tmpInnerA = innerA * step;
            uint64_t tmpOuterA = outerA / axislen * OpsUtils::CeilDiv(axislen, step);
            double rate = (double)tmpOuterA / (double)(OpsUtils::CeilAlign(tmpOuterA, compileInfo_.vectorCoreNum));
            bool isContinue = (rate > THRES_HOLD && tmpInnerA * innerR * cBlock_.size <= bBlockNum &&
                               tmpInnerA * cBlock_.aSize < maxInnerA);
            if (isContinue) {
                continue;
            } else {
                step = step > 1 ? step - 1 : step;
                splitHere = true;
                break;
            }
        }
        if (splitHere) {
            innerA *= step;
            outerA = outerA / axislen * OpsUtils::CeilDiv(axislen, step);
            break;
        }
        innerA *= axislen;
        outerA /= axislen;
    }
    unitA_.Update(iA, innerA, outerA, step);
    return true;
}

template <class Pattern>
void CalcBasicBlock()
{
    if (Pattern::ID == PATTERN_A) {
        basicBlock_ = BASIC_BLOCK;
        return;
    }
    if (opInput_.inputDtype != opInput_.promoteDtpye) {
        basicBlock_ = BASIC_BLOCK / CONST2;
    } else {
        basicBlock_ = BASIC_BLOCK;
    }
}

template <class Pattern>
bool IsEmtpyTensor(const std::vector<uint64_t>& shape)
{
    for (int32_t i = 0; i < Pattern::Dim; i++) {
        if (shape[i] == 0) {
            return true;
        }
    }
    return false;
}
template <class Pattern>
bool ComputeEmptyTiling(std::vector<uint64_t>& shape)
{
    uint64_t s = 1;
    for (int32_t dim = Pattern::Dim - 1; dim > -1; dim--) {
        if (IsAxisA<Pattern>(dim)) {
            s *= shape[dim];
        }
    }
    param_->tilingData.outSize = s;
    if (s == 0) {
        return true;
    }
    basicBlock_ = BASIC_BLOCK;
    std::vector<uint64_t> newshape(MAX_DIM, s);
    if (!CalcCacheLineStep<ATVC::ReducePattern::A>(newshape)) { return false; }
    unitA_.outer *= cBlock_.cacheLineOuter;
    ComputeUnitA<ATVC::ReducePattern::A>(newshape);
    ComputeSplit<ATVC::ReducePattern::A>(newshape);
    return true;
}

template <class Pattern>
bool ComputeTiling(std::vector<uint64_t>& shape)
{
    // Tiling can be divided into three steps
    // First: find the cacheLine-aligned axis from the innermost axis
    // Second: All the axes inside the CacheLine are a single unit
    //  1. The A-axis in UB is multiplied by the A-axis in CacheLine, and the result is less than one threshold(128)
    //  2. The A-axis outside UB, should ensure that can be cutted by enough cores
    //  3. The rest of the space for the basicblock is reserved for the R-axis
    CalcBasicBlock<Pattern>();
    if (IsEmtpyTensor<Pattern>(shape)) {
        return ComputeEmptyTiling<Pattern>(shape);
    }
    if (!CalcCacheLineStep<Pattern>(shape)) { return false; }
    int32_t axisInCacheLine = cBlock_.axis;
    for (int32_t i = Pattern::FirstA ? 0 : 1; i < axisInCacheLine; i += CONST2) {
        unitA_.outer *= shape[i];
    }
    for (int32_t i = Pattern::FirstA ? 1 : 0; i < axisInCacheLine; i += CONST2) {
        unitR_.outer *= shape[i];
    }

    bool basicSplitA = (axisInCacheLine + (Pattern::FirstA ? 1 : 0)) % CONST2;
    if (basicSplitA) {
        unitA_.outer *= cBlock_.cacheLineOuter;
    } else {
        unitR_.outer *= cBlock_.cacheLineOuter;
    }
    ComputeUnitA<Pattern>(shape);
    ComputeUnitR<Pattern>(shape);

    if (!ComputeExtraUnitA<Pattern>(shape)) { return false; }

    ComputeSplit<Pattern>(shape);
    SetTilingKey<Pattern>();
    return true;
}

template <class Pattern>
void ComputeSplit(std::vector<uint64_t>& shape)
{
    uint64_t cacheStep = cBlock_.cacheLineStep;
    int32_t axis = cBlock_.axis;
    uint64_t perCoreNum = OpsUtils::CeilDiv(unitA_.outer * unitR_.outer, compileInfo_.vectorCoreNum);
    uint64_t blockDim = OpsUtils::CeilDiv(unitA_.outer * unitR_.outer, perCoreNum);
    uint64_t factorA = unitA_.idx == axis ? unitA_.step * cacheStep : unitA_.step;
    uint64_t factorR = unitR_.idx == axis ? unitR_.step * cacheStep : unitR_.step;

    uint64_t rStepNum = unitR_.idx < 0 ? 1 : OpsUtils::CeilDiv(shape[unitR_.idx], factorR);
    uint64_t totalOutNoStepR = unitR_.outer / rStepNum * unitA_.outer;
    for (auto iR = unitR_.idx; iR > -1;) {
        if (totalOutNoStepR < blockDim) {
            auto tmpBlockDim = OpsUtils::CeilAlign(blockDim, totalOutNoStepR);
            if (tmpBlockDim <= compileInfo_.vectorCoreNum) {
                blockDim = tmpBlockDim;
            } else {
                blockDim = OpsUtils::FloorAlign(blockDim, totalOutNoStepR);
            }
            break;
        }
        iR -= CONST2;
        if (iR > 0) {
            totalOutNoStepR /= shape[iR];
        }
    }

    param_->tilingData.ubFactorA = factorA;
    uint64_t factorACntPerCore = OpsUtils::CeilDiv(unitA_.outer, blockDim);
    param_->tilingData.factorACntPerCore = factorACntPerCore;
    param_->tilingData.factorATotalCnt = unitA_.outer;

    param_->tilingData.ubFactorR = factorR;
    uint64_t factorRCntPerCore = OpsUtils::CeilDiv(unitR_.outer, OpsUtils::CeilDiv(blockDim, unitA_.outer));
    param_->tilingData.factorRCntPerCore = factorRCntPerCore;
    param_->tilingData.factorRTotalCnt = unitR_.outer;
    param_->tilingData.groupR = OpsUtils::CeilDiv(unitR_.outer, factorRCntPerCore);
    for (size_t i = 0; i < MAX_DIM; i++) {
        param_->tilingData.shape[i] = shape[i];
    }
    param_->tilingData.basicBlock = basicBlock_;
    param_->tilingData.coreNum = static_cast<int32_t>(compileInfo_.vectorCoreNum);
    ComputeStride<Pattern>(shape);
    uint32_t realCore = OpsUtils::CeilDiv(unitA_.outer, factorACntPerCore) * OpsUtils::CeilDiv(unitR_.outer, factorRCntPerCore);
    param_->tilingData.coreNum = realCore;
}
template <class Pattern>
bool IsAxisA(int32_t idx)
{
    if (Pattern::FirstA) {
        return idx % CONST2 == 0;
    } else {
        return idx % CONST2 == 1;
    }
}

template <class Pattern>
bool CalcCacheLineStep(const std::vector<uint64_t>& shape)
{
    // find the cacheLine-aligned axis
    // cacheLineStep record cacheLine-aligned axis's shape, while left is cacheLineOuter
    uint64_t dTypeSize = ge::GetSizeByDataType(opInput_.inputDtype);
    if (dTypeSize == 0) {
        printf("[Reduce Tiling] input dtype size cannot be zero!\n");
        return false;
    }
    uint64_t cacheSize = compileInfo_.cacheLineSize / dTypeSize;
    uint64_t ubBlockSize = compileInfo_.ubBlockSize / dTypeSize;
    uint64_t cacheLineShape = 1;
    uint64_t cacheLineStep = 1;
    uint64_t cacheLineOuter = 1;
    uint64_t aInCacheLine = 1;
    for (int32_t i = Pattern::Dim - 1; i > -1; --i) {
        cacheLineShape *= shape[i];
        if (cacheLineShape > cacheSize) {
            cacheLineShape /= shape[i];
            cacheLineStep = OpsUtils::CeilDiv(cacheSize, cacheLineShape);
            cacheLineShape *= cacheLineStep;
            cacheLineOuter = OpsUtils::CeilDiv(shape[i], cacheLineStep);
            cBlock_.axis = i;
            break;
        } else {
            cacheLineStep = shape[i];
            cBlock_.axis = i;
        }
    }
    for (int32_t i = Pattern::TailA ? Pattern::Dim - 1 : Pattern::Dim - CONST2; i > cBlock_.axis; i -= CONST2) {
        if (i == Pattern::Dim - 1) {
            aInCacheLine *= OpsUtils::CeilAlign(shape[i], ubBlockSize);
        } else {
            aInCacheLine *= shape[i];
        }
    }
    bool basicSplitA = (cBlock_.axis + (Pattern::FirstA ? 1 : 0)) % CONST2;
    if (basicSplitA) {
        aInCacheLine *= cacheLineStep;
    }
    for (int32_t i = Pattern::Dim - 1; i > cBlock_.axis; --i) {
        if (i == Pattern::Dim - 1) {
            cBlock_.size = cBlock_.size * OpsUtils::CeilAlign(shape[i], ubBlockSize);
        } else {
            cBlock_.size *= shape[i];
        }
    }
    cBlock_.size *= cacheLineStep;
    cBlock_.cacheLineStep = cacheLineStep;
    cBlock_.cacheLineOuter = cacheLineOuter;
    cBlock_.aSize = aInCacheLine;
    return true;
}

template <class Pattern>
void ComputeUnitA(const std::vector<uint64_t>& shape)
{
    int32_t axisInCacheLine = cBlock_.axis;
    uint64_t outerA = unitA_.outer;
    uint64_t innerA = unitA_.inner;
    uint64_t maxCacheA = MAX_INNER_A * sizeof(float) / ge::GetSizeByDataType(opInput_.promoteDtpye);
    uint64_t maxInnerA =
        Pattern::ID == PATTERN_A ? basicBlock_ / ge::GetSizeByDataType(opInput_.inputDtype) : maxCacheA;
    uint64_t stepLen = Pattern::ID == PATTERN_A ? A_STEP_LEN : 1;  // 纯A的步长为4, 减少循环次数
    bool basicSplitA = (axisInCacheLine + (Pattern::FirstA ? 1 : 0)) % CONST2;
    uint64_t bBlockNum = basicBlock_ / ge::GetSizeByDataType(opInput_.inputDtype);
    uint64_t step = 1;
    int32_t iA;
    for (iA = basicSplitA ? axisInCacheLine : axisInCacheLine - 1; iA > -1; iA -= CONST2) {
        uint64_t axislen = ((iA == axisInCacheLine) ? cBlock_.cacheLineOuter : shape[iA]);
        bool splitHere = false;
        uint64_t maxStep = 0;
        for (step = 1; step <= axislen / stepLen; step++) {
            uint64_t s = step * stepLen;
            uint64_t tmpInnerA = innerA * s;
            uint64_t tmpOuterA = outerA / axislen * OpsUtils::CeilDiv(axislen, s);
            double rate = (double)tmpOuterA / (double)(OpsUtils::CeilAlign(tmpOuterA, compileInfo_.vectorCoreNum));
            uint64_t aSize = tmpInnerA * cBlock_.aSize;
            if (iA == axisInCacheLine) {
                aSize = (cBlock_.aSize / cBlock_.cacheLineStep) * std::min(cBlock_.cacheLineStep * s, shape[iA]);
            }
            if (aSize <= maxInnerA && tmpInnerA * cBlock_.size <= bBlockNum) {
                maxStep = rate > THRES_HOLD ? step : maxStep;
            } else {
                step = step > 1 ? step - 1 : step;
                splitHere = true;
                break;
            }
        }
        if (splitHere || maxStep <= 1 || iA - CONST2 < 0) {
            step = maxStep == 0 ? 1 : maxStep * stepLen;
            innerA *= step;
            outerA = outerA / axislen * OpsUtils::CeilDiv(axislen, step);
            break;
        }
        innerA *= axislen;
        outerA /= axislen;
    }
    unitA_.Update(iA, innerA, outerA, step);
}

template <class Pattern>
void ComputeUnitR(std::vector<uint64_t>& shape)
{
    int32_t axisInCacheLine = cBlock_.axis;
    uint64_t outerR = unitR_.outer;
    uint64_t innerR = unitR_.inner;
    uint64_t outerA = unitA_.outer;
    uint64_t innerA = unitA_.inner;
    uint64_t step = 1;
    uint64_t bBlockNum = basicBlock_ / ge::GetSizeByDataType(opInput_.inputDtype);
    bool basicSplitA = (axisInCacheLine + (Pattern::FirstA ? 1 : 0)) % CONST2;
    int32_t iR;
    for (iR = basicSplitA ? axisInCacheLine - 1 : axisInCacheLine; iR > -1; iR -= CONST2) {
        uint64_t axislen = shape[iR];
        if (iR == axisInCacheLine) {
            axislen = cBlock_.cacheLineOuter;
        }
        innerR *= axislen;
        if (innerR * innerA * cBlock_.size <= bBlockNum) {
            outerR = outerR / axislen;
            continue;
        }

        innerR /= axislen;
        // maybe bBlockNum not full
        step = std::min(bBlockNum / (innerA * innerR * cBlock_.size), axislen);
        for (; step > 1; step--) {
            auto tmpOuterR = outerR / axislen * OpsUtils::CeilDiv(axislen, step);
            double rate = (double)(outerA * tmpOuterR) /
                          (double)(OpsUtils::CeilAlign(outerA * tmpOuterR, compileInfo_.vectorCoreNum));
            if (rate > THRES_HOLD) {
                innerR *= step;
                outerR = outerR / axislen * OpsUtils::CeilDiv(axislen, step);
                break;
            }
        }
        break;
    }
    unitR_.Update(iR, innerR, outerR, step);
}

private:
    int32_t basicBlock_ = 0;
    ATVC::ReduceParam* param_ {nullptr};
    ATVC::ReducePolicy* policy_ {nullptr};
    ATVC::OpCompileInfo compileInfo_;
    ATVC::CacheLineBlock cBlock_;
    ATVC::ReduceTilingUnit unitA_;
    ATVC::ReduceTilingUnit unitR_;
    ATVC::ReduceTilingInputParam opInput_;
};  // class ReduceOpTiling
}  // namespace OpTiling

#endif